%include "pm.inc"

org 0900h

jmp REAL_CODE

[SECTION .gdt]
LABEL_GDT: Descriptor 0, 0, 0
LABEL_CODE32: Descriptor 0, 0xfffff, DA_CR|DA_32|DA_LIMIT_4K
LABEL_DESC_DATA_STACK: Descriptor 0, 0xfffff, DA_DRW|DA_LIMIT_4K|STACK_MODE
LABEL_VEDIO: Descriptor 0B8000h, 0ffffh, DA_DRW
LABLE_DESC_DATA:Descriptor 0, DATA_LEN - 1, DA_DR

GDT_LEN equ $ - LABEL_GDT
GDTPtr: dw GDT_LEN - 1
dd 0

;Selctor
SelectorCode32 equ LABEL_CODE32 - LABEL_GDT
SelectorData_Stack equ LABEL_DESC_DATA_STACK - LABEL_GDT
SelectorVedio equ LABEL_VEDIO - LABEL_GDT
SelectorData equ LABLE_DESC_DATA - LABEL_GDT

[SECTION .data]
LABEL_DATA:
	MemSize: dd 0;Store memory size.
	ProtectStr: db "In protect mod now!", 0
	ProtectStrLen equ $ - ProtectStr - 1
	VirtualModStr: db "Paging complete.", 0
	VirtualModStrOffset equ VirtualModStr - LABEL_DATA
	VirtualModStrLen equ $ - VirtualModStr

	ARDSPtr:;Store ARDS  structrue.
		%rep 10
			dd 0;BaseAddrLow
			dd 0;BaseAddrHigh
			dd 0;LengthLow
			dd 0;LenthHign
			dd 0;Type
		%endrep
	DATA_LEN equ $ - LABEL_DATA

[SECTION .code16]
[BITS 16]
REAL_CODE:
	mov ax, cs
	mov ds, ax
	mov es, ax
	mov ss, ax
	mov sp, 0100h

	;Set base address for SelectorData
	mov ax, cs
	movzx eax, ax
	shl eax, 4
	add eax, LABEL_DATA
	mov [LABLE_DESC_DATA + 2], ax
	shr eax, 16
	mov [LABLE_DESC_DATA + 4], al
	mov [LABLE_DESC_DATA + 7], ah

	call get_memory

	;Load GDTR
	xor eax, eax
	mov ax, cs
	shl eax, 4
	add eax, LABEL_GDT
	mov dword [GDTPtr + 2], eax
	lgdt [GDTPtr]

	;Close real mod interrupt
	cli

	;Open A20
	in al, 092h
	or al, 000010h
	out 092h, al

	;Set the protect bit of cr0 to 1
	mov eax, cr0
	or eax, 1
	mov cr0, eax

	;Jump to protect mod code
	jmp dword SelectorCode32:CODE32;dword decretor the offset.

	get_memory:
		mov ax ,0xe820;Call number 0xe820 of 0x15 interrupt 
		xor ebx, ebx
		mov di, ARDSPtr
		mov ecx, 20
		mov edx, 0x534d4150
		xor si, si;esi stores the number of ARDS.
		.1:
			;Call in 0x15 to get the memory infomation.
			int 0x15
			;Some delay to wait for interuption.
			jc .m2;If CF=1,jump to .end.
			nop
			nop
			nop
			nop
			inc si
			add di, 20 ;di add 20 to let [es:di] store the next ARDS.
			cmp ebx, 0 ;ebx is 0 implies the last ARDS.
			jnz .1
		.e:
			cmp si, 0
			jz .m2
			;Find the max BaseAddr+Length which means the memory.
			xor ebx, ebx;Reserve the max value.
			xor di, di
			mov cx, si
		.max:
			mov eax, dword [ARDSPtr+di];Low bass address bits.
			mov edx, dword [ARDSPtr+di+8];Low length bits
			add eax, edx;BaseAddr+Length
			add di, 20
			cmp eax, ebx
			jng .notmove
			mov ebx, eax
		.notmove:
			loop .max
			cmp ebx, 0
			jz .m2
			mov dword [MemSize], ebx
			jmp .end
		.m2:
			mov ax, 0xe801;Call number 0xe801 of 0x15 interrupt 
			int 0x15
			jc .m3;If CF = 1,it means error occures,jump to method3.
			movzx eax, ax;Memory size below 16 mb.
			shl eax, 10;Unit is 1k,so multiply 1024,name as shift left 10 bits.
			movzx ebx, bx;Memory size beyond 16 mb.
			shl ebx, 16;Unit is 64k
			add ebx, eax
			add ebx, 0x100000;Add the hole of 1m.
			mov dword [MemSize], ebx
			jmp .end

		.m3:
			mov ax, 0x88;Call number 0xe88 of 0x15 interrupt 
			int 0x15
			jc .end
			movzx eax, ax
			shl eax, 10
			add eax, 0x100000
			mov dword [MemSize], eax
		.end:
			ret

[SECTION .code32]
ALIGN 32
[BITS 32]
CODE32:
	mov ax, SelectorVedio
	mov gs, ax
	mov ax, SelectorData_Stack
	mov ds, ax
	mov ax, SelectorData_Stack
	mov ss, ax
	mov sp, 0x1000

	;Display string:"In protect mod now!".
	mov ecx, ProtectStrLen
	mov ebx, ProtectStr;Offset from data section of the string.
	mov edx, ((80*11) + 40)*2
	call DispStr

	call Paging

	;Adjust GDTPtr ,vedio base and stack.
	sgdt [GDTPtr]
	mov eax, [GDTPtr+2]
	or dword [eax+24+4], 0xc0000000
	add dword [GDTPtr+2], 0xc0000000
	;mov esp, 0xc009f000
	;add esp, 0xc0000000

	;Open paging
	mov eax, PageDirectory
	mov cr3, eax
	mov eax, cr0
	or eax, 080000000h
	mov cr0, eax

	lgdt [GDTPtr];Reload GDTPtr after paging.

	;Display string:"Paging complete."
	;mov ax ,SelectorData
	;mov ds, ax
	mov ecx, VirtualModStrLen
	mov ebx, VirtualModStr
	mov edx, ((80*12) + 40)*2
	call DispStr

	;Load loader.bin
	mov cx, 200;Sector number.Remember to modify it when kernel becomes bigger.
	mov ebx, 9;Sector LBA.
	Kernel_Elf_Buff equ 0x7000
	call Read_Disk

	;Parse loader.Copy kernel to right position.Then jump to kernel entry.
	call ParseKernel
	mov esp, 0xc009f000;Move esp to kernel pcb.
	jmp SelectorCode32:0xc0001500
	jmp $
	DispStr:
		mov ah, 0ch
		mov al, byte [ebx] 
		mov [gs:edx], ax
		inc ebx
		add edx, 2
		loop DispStr
		ret

	Paging:
		;Properties of page directory entry and page table.
		P equ 1B
		NP equ 0B
		RW_W equ 10B
		RW_R equ 00B
		US_U equ 100B
		US_S equ 000B

		;Address of page directory and page table.
		PageDirectory equ 0100000h;01000000h is the physical address of page directory.
								  ;Kernel is localed at the top 1M of virtual memory space.
								  ;Kernel will be mapped to the bottom 1M of physical memory space.

		PageTable equ 0101000h;Page table address should be aligned by 4k.

		;mov ax, SelectorPage;Make sure to access to whole memery.
		;mov ds, ax

		;Clear page directory entries.
		mov ecx, 4096
		mov eax, PageDirectory
		clear:
			mov byte [eax], 0
			inc eax
			loop clear

		mov eax, PageDirectory
		mov ebx, PageTable
		or ebx, P|RW_W|US_U;Page directory entry with properties of presention, writeable and readable and user privilege.
		mov [eax], ebx
		mov [eax+768*4], ebx;Virtual address 3G is the 768th entries of page directory.
		;After set the page direction,mapping from 0~1M of virtuanl space to 0~1M of physical space completes.
		;The first level page table only contains 0,pointed to the 0 address of physical space.But a page table only contains
		;4k space.We need 1M space.So we need to create another 255 page table entries.
		;Maybe should assign some properties for page table.
		;Create page table entry for 0~1M physical space.
		mov ecx, 256;1M contains 256 page table entries.
		mov eax, PageTable
		mov ebx, 0;Physical address.
		xor esi, esi
		pte:
			and ebx, 0xfffff000;Reserve 20 most significant bits.
			or ebx, P|RW_W|US_U
			mov dword [eax + esi], ebx
			add ebx, 4096;One page table entry contains 4k space.
			add esi, 4;One page table entry is 4 byte.
			loop pte

		;Kernel virtual space should be set in advance so that all processes can share it.
		;Create page directory entries for the remain kernel space undering that two entries haven been assigned ,one for 3~3.004G,another for 3.996~4G.
		mov ecx, 254
		mov eax, PageTable
		add eax, 4096;The second page table address.
		mov ebx, PageDirectory
		add ebx, 769*4;The second page directory entry of 3~4G is 769th of the whole entries.
		;xor esi, esi
		kernel_pde:
			and eax, 0xfffff000
			or eax, P|RW_W|US_U;Don't ignore privilege,otherwise it will cause can't access to memory.
			mov dword [ebx], eax
			add ebx, 4
			add eax, 4096
			loop kernel_pde

		;Map the last entry of virtual space to the address of page directory.
		mov eax, PageDirectory
		and eax, 0xfffff000
		or eax, P|RW_W|US_U;Don't ignore privilege,otherwise it will cause can't access to memory.
		xchg bx, bx
		mov [ebx], eax

		ret

	Read_Disk:

		;Set sector number.
		mov dx, 0x1f2
		mov al, cl
		out dx, al

		;Set LBA
		;Low 8 bits of lba
		mov dx, 0x1f3
		mov al, bl
		out dx, al

		;Middle 8 bit of lba
		mov dx, 0x1f4
		mov al, bh
		out dx, al

		;High 8 bits of lba
		mov dx, 0x1f5
		shr ebx, 16
		mov al, bl
		out dx, al

		;The last 4bits of lba, and set the 6th bits of this register to open lba mode.
		mov dx, 0x1f6
		shr ebx, 8
		mov al, bl
		or al, 01000000b
		out dx, al

		mov dx, 0x1f7
		Check_Busy:
			in al, dx
			mov ah, al
			and ah, 10000000b
			cmp ah, 10000000b
			jz Check_Busy

		Check_Error:
			mov ah, al
			and ah, 01b
			cmp ah, 01b
			jnz Check_Command
			jmp $

		Check_Command:
			in al, dx
			;mov ah, al
			and al, 01000000b
			cmp al, 01000000b
			jnz Check_Command

			;Set command
			mov al, 0x20
			out dx, al

			;
		Check_Busy1:
			in al, dx
			mov ah, al
			and ah, 10000000b
			cmp ah, 10000000b
			jz Check_Busy1

		Check_Error1:
			mov ah, al
			and ah, 01b
			cmp ah, 01b
			jnz Check_Data_Ready
			jmp $

		Check_Data_Ready:
			in al, dx
			and al, 00001000b
			cmp al, 00001000b
			jnz Check_Data_Ready

			;Read data from 0x1f0
			mov eax, 256
			;movzx cx, cl 
			mul cx
			mov ecx, eax

			mov ebx, Kernel_Elf_Buff
			mov dx, 0x01f0
		Read_Date:
			in ax, dx
			mov [ebx], ax
			add ebx, 2
			loop Read_Date

			ret

	ParseKernel:
		mov ax, SelectorData_Stack
		mov es, ax

		;Get the offset of first program header.
		mov eax, [Kernel_Elf_Buff+0x1c]
		;The address of first program header.
		add eax, Kernel_Elf_Buff
		;Size of program header
		mov bx, word [Kernel_Elf_Buff+0x2a]
		;Numbers of program header
		mov cx, word [Kernel_Elf_Buff+0x2c]
		
		movzx ebx, bx
		;xor esi, esi
		Load_segment:
			;Program header type
			mov edx, [eax]
			cmp edx, 0
			jz Jump_Copy
			
			
			mov edx, [eax + 0x08]
			cmp edx, 0
			jz Jump_Copy
			mov edx, [eax + 0x10]
			cmp edx, 0
			jz Jump_Copy
			;The offset of segment of program header.
			mov edx, [eax + 4]
			add edx, Kernel_Elf_Buff
			
			;Push the segment address.  
			push edx
			;Push the virtual address of the segment as the argument of Mem_Cpy.
			push dword [eax + 0x08]
			;Push the size of the segment as the argument of Mem_Cpy.
			push dword [eax + 0x10]
			;push edx
			call Mem_Cpy
			add esp, 12
		Jump_Copy:
			add eax, ebx
			loop Load_segment

			ret
		Mem_Cpy:
			push ebp
			mov ebp, esp
			push ecx
			push eax
			mov esi, [ebp + 16]
			mov edi, [ebp + 12]
			mov ecx, [ebp + 8]
		Copy_Byte:
			mov al, byte [ds:esi]
			mov byte [es:edi], al
			inc esi
			inc edi
			loop Copy_Byte
			pop eax
			pop ecx
			pop ebp

			ret




